/*********************************************************************
 * WorkerThread.java created on Feb 3, 2010
 *
 * This file is part of Sure Tech Support Portal System. 
 * Copyright @ 2010 Sure Tech (HK) Limited. All rights reserved.
 * Sure Tech PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *********************************************************************/
package com.bizdigger.thread.pool;

import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @Author: Mac Wang
 * @Date: Feb 3, 2010 2:57:27 PM 
 * @Email: mac@suretech.com.hk
 */

public class WorkerThread extends Thread {
	private Log log = LogFactory.getLog(WorkerThread.class);
	
	/**
	 * Keeps the thread running when false. When set to true, completes the
	 * execution of the thread and stops.
	 */
	private boolean stopped = false;
	
	/**
	  * Manages the thread's state. When the thread is first created, running is
	  * set to false. When the thread is assigned a task for the first time, the
	  * thread continues to be in the running state until stopped.
	  */
	private boolean running = false;
	
	/**
	  * Manages the thread's internal state with respect to the task. When the
	  * thread finishes executing the task, this is set to true.
	  */
	private boolean done = false;
	
	/**
	  * The class name where the method to be execute is defined.
	  */
	private String className;
	private String methodName;
	private Object[] methodParams;
	private Class[] paramTypes;
	
	/**
	  * The object to synchronize upon for notifying the completion of task.
	  */
	private Object syncObject = null;
	
	/**
	  * The result of our execution.
	  */
	private Object result = null;
	
	/**
	  * The pool being used. We use this if we need to return the object back to the
	  * pool. If this is not set, we assume that the client will take care of returning
	  * the object back to the pool.
	  */
	private ThreadPool pool = null;
	
	public WorkerThread(){
	}
	
	public void setPool(ThreadPool pool) {
		this.pool = pool;
	}

	public boolean isRunning() {
		return running;
	}

	public void setRunning(boolean running) {
		this.running = running;
	}

	public boolean isDone() {
		return done;
	}

	public void setDone(boolean done) {
		this.done = done;
	}

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public String getMethodName() {
		return methodName;
	}

	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}

	public Object[] getMethodParams() {
		return methodParams;
	}

	public void setMethodParams(Object[] methodParams) {
		this.methodParams = methodParams;
	}

	public Class[] getParamTypes() {
		return paramTypes;
	}

	public void setParamTypes(Class[] paramTypes) {
		this.paramTypes = paramTypes;
	}

	public Object getSyncObject() {
		return syncObject;
	}

	public void setSyncObject(Object syncObject) {
		this.syncObject = syncObject;
	}

	public Object getResult() {
		return result;
	}

	public void setResult(Object result) {
		this.result = result;
	}

	public boolean isStopped() {
		return stopped;
	}

	public void setStopped(boolean stopped)
	{
		this.stopped = stopped;
	}
	
	public synchronized void execute(String className, String methodName, Object[] methodParams, Class[] paramTypes, Object syncObject)
	{
		this.className = className;
		this.methodName = methodName;
		this.methodParams = methodParams;
		this.paramTypes = paramTypes;
		this.syncObject = syncObject;
		this.done = false;
		
		if(!running)	//If this is the first time, then kick off the thread.
		{
			this.setDaemon(true);
			this.start();
		}
		else	// we already have a thread running so wakeup the waiting thread.
		{
			this.notifyAll();
		}
	}
	
	private void execute()
	{
		try
		{
			Class cls = getClass(this.getClassName());
			Object obj = cls.newInstance();
			
			this.result = MethodUtils.invokeExactMethod(obj, this.getMethodName(), this.getMethodParams(), this.getParamTypes());
		    log.debug(" #### Execution Result = " + result + " for : " + this);
		}
		catch(ClassNotFoundException e)
		{
			log.error("ClassNotFoundException-", e);
		}
		catch(InstantiationException e)
		{
			log.error("InstantiationException-", e);
		}
		catch(InvocationTargetException e)
		{
			log.error("InvocationTargetException-", e);
		}
		catch(IllegalAccessException e)
		{
			log.error("IllegalAccessException-", e);
		}
		catch(NoSuchMethodException e)
		{
			log.error("NoSuchMehtodexception-", e);
		}
	}
	
	private static Class getClass(String name) throws ClassNotFoundException
	{
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		if(loader == null)
		{
			loader = WorkerThread.class.getClassLoader();
		}
		return loader.loadClass(name);
	}
	
	public void run()
	{
		running = true;
		while(!stopped)	// The thread keeps running.
		{
			if(done)	// We are waiting for a task now.
			{
				synchronized(this)
				{
					try
					{
						this.wait();	//Wait until we get a task to execute.
					}
					catch(InterruptedException e)
					{
						stopped = true;
						log.error("", e);
					}
				}
			}
			else	//there is a task....let us execute it.
			{
				try
				{
					this.execute();
				}
				catch(Exception e)
				{
					log.error("", e);
				}
				finally
				{
					if(syncObject != null)
					{
						synchronized(syncObject)
						{
							syncObject.notify();	//Notify the completion.
						}
					}
					reset();
					returnToPool();
				}
			}
		}
	}
	
	public void reset()
	{
		this.done = true;
	    this.className = null;
	    this.methodName = null;
	    this.methodParams = null;
	    this.paramTypes = null;
	    this.syncObject = null;
	}
	
	private void returnToPool()
	{
		if(pool != null)
		{
			try
			{
				pool.returnObject(this);
			}
			catch(Exception e)
			{
				log.error("err-", e);
			}
			pool = null;
		}
	}
}
